package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.gmall.gateway.config.JwtProperties;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component
@EnableConfigurationProperties(JwtProperties.class)
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.PathConfig> {

    @Autowired
    private JwtProperties properties;

    public AuthGatewayFilterFactory() {
        super(PathConfig.class);
    }

    //指定参数接收顺序
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");
    }

    //指定接收参数的类型
    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public GatewayFilter apply(PathConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //编写登录拦截业务
                //获取request 和 response对象
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();

                // 1判断当前请求是否在拦截名单之中 不在则放行
                String curPath = request.getURI().getPath(); //当前请求路径

                List<String> paths = config.paths; //需要拦截的路径

                //如果拦截名单不为空 并且当前路径不在拦截名单之中 放行
                if (!CollectionUtils.isEmpty(paths) && !paths.stream().anyMatch(path->curPath.startsWith(path))){
                    //放行
                    return chain.filter(exchange);
                }

                // 2 获取token 请求头或者cookie携带
                String token = request.getHeaders().getFirst(properties.getToken());

                //如果请求头中没有token信息 再从cookie中获取
                if (StringUtils.isBlank(token)){
                    //同一个key对应的cookie的值可能有多个 尝试从cookie中获取token信息
                    MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                    if (!CollectionUtils.isEmpty(cookies) && cookies.containsKey(properties.getCookieName())){
                        //获取对应的cookie对象
                        HttpCookie cookie = cookies.getFirst(properties.getCookieName());
                        //获取cookie的value值 也就是token
                        token = cookie.getValue();
                    }
                }

                // 3 判断token是否为空 如果为空 重定向到登录界面 结束请求
                if (StringUtils.isBlank(token)) {
                    //token为空 重定向
                    return redirectLogin(request, response);
                }

                try {
                    // 4 解析token中的信息 解析出错 重定向到登录界面 结束请求
                    Map<String, Object> map = JwtUtils.getInfoFromToken(token, properties.getPublicKey());

                    // 5 获取当前请求的ip地址 与token中的ip地址比较 如果不同 则跳转到登录界面
                    //获取token中的ip
                    String tokenIp = map.get("ip").toString();
                    //获取本次请求的ip
                    String curIp = IpUtils.getIpAddressAtGateway(request);
                    if (!StringUtils.equals(tokenIp,curIp)) {
                        //ip不相同 盗用 重新登录
                        return redirectLogin(request, response);
                    }

                    // 6 将获取到的信息 （userId userName）携带到其他页面
                    request.mutate().header("userId",map.get("userId").toString())
                                    .header("userName",map.get("userName").toString()).build();
                    exchange.mutate().request(request).build();
                    // 7 放行
                    return chain.filter(exchange);
                } catch (Exception e) {
                    e.printStackTrace();
                    //解析出错 重定向
                    return redirectLogin(request, response);
                }
            }
        };
    }

    private Mono<Void> redirectLogin(ServerHttpRequest request, ServerHttpResponse response) {
        //设置状态码 表示本次请求重定向
        response.setStatusCode(HttpStatus.SEE_OTHER);
        //获取响应头 给响应头设置重定向地址
        response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
        //结束本次请求
        return response.setComplete();
    }

    @Data
    public static class PathConfig{
        private List<String> paths;
    }
}
